package com.harmontronics.hanma.paas.paas.domain.app.service;

import cn.hutool.core.bean.BeanUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.harmontronics.hanma.paas.paas.consts.NumberEnum;
import com.harmontronics.hanma.paas.paas.domain.app.entity.HmProcess;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.BpmnConstant;
import com.harmontronics.hanma.paas.paas.domain.app.entity.valueobject.I18nKeyEnum;
import com.harmontronics.hanma.paas.paas.domain.app.repository.persistence.HmModelRepository;
import com.harmontronics.hanma.paas.paas.domain.app.repository.persistence.HmPageRepository;
import com.harmontronics.hanma.paas.paas.domain.app.repository.persistence.HmProcessRepository;
import com.harmontronics.hanma.paas.paas.domain.app.repository.po.HmModelPO;
import com.harmontronics.hanma.paas.paas.domain.app.repository.po.HmPagePO;
import com.harmontronics.hanma.paas.paas.domain.app.repository.po.HmProcessPO;
import com.harmontronics.hanma.paas.paas.exception.ErrorKeyException;
import com.harmontronics.hanma.paas.paas.utils.JacksonUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import org.flowable.common.engine.impl.util.io.StreamSource;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.DeploymentBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author ldb
 */
@Service("processImportService")
public class ProcessImportService {

    private final Logger logger = LoggerFactory.getLogger(ProcessImportService.class);
    @Autowired
    private HmProcessRepository hmProcessRepository;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HmModelRepository hmModelRepository;

    @Autowired
    private HmPageRepository hmPageRepository;

    @Transactional(rollbackFor = Exception.class)
    public void importProcess() throws IOException {
        File homeDir = new ApplicationHome(getClass()).getDir();
        File processDir = new File(homeDir, "process");
        if (!processDir.exists()) {
            return;
        }
        File[] jsonFiles = processDir.listFiles(new ProcessFileFilter(".json"));
        File[] xmlFiles = processDir.listFiles(new ProcessFileFilter(".xml"));

        Map<String, File> nameMap = Arrays.stream(xmlFiles).collect(Collectors.toMap(File::getName, Function.identity()));
        ObjectMapper objectMapper = JacksonUtils.getObjectMapper();
        for (File jsonFile : jsonFiles) {
            String filename = jsonFile.getName();
            String idStr = filename.replace(".json", "");
            Long processId = Long.parseLong(idStr);
            HmProcessPO dbPo = hmProcessRepository.getById(processId);
            //已存在
            if (dbPo != null) {
                logger.info("process:{}已存在", idStr);
                continue;
            }
            File xmlFile = nameMap.get(idStr + ".xml");
            if (xmlFile == null) {
                throw new FileNotFoundException(idStr + ".xml");
            }
            String xmlStr = new String(Files.readAllBytes(xmlFile.toPath()));
            String jsonStr = new String(Files.readAllBytes(jsonFile.toPath()));
            HmProcess process = objectMapper.readValue(jsonStr, HmProcess.class);
            HmProcessPO processPO = BeanUtil.toBean(process, HmProcessPO.class);
            Deployment deployment = doDeployment(xmlStr, processPO);
            processPO.setDeploymentId(deployment.getId());

            List<HmProcessPO> dbPos = hmProcessRepository.findByKey(process.getKey());
            for (HmProcessPO po : dbPos) {
                if (po.getActivated() == NumberEnum.ONE.getNum()) {
                    po.setActivated(NumberEnum.ZERO.getNum());
                    hmProcessRepository.updateById(po);
                }
            }
            hmProcessRepository.save(processPO);
        }
    }


    private Deployment doDeployment(String json, HmProcessPO po) {
        String resourceName = po.getKey() + "_" + po.getVersion() + ".bpmn";
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
        deploymentBuilder.addBytes(resourceName, json.getBytes(StandardCharsets.UTF_8));
        StreamSource xmlSource = new BytesStreamSource(json.getBytes(StandardCharsets.UTF_8));
        BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xmlSource, false, false, "UTF-8");
        org.flowable.bpmn.model.Process process = bpmnModel.getMainProcess();
        Collection<FlowElement> flowElements = process.getFlowElements();
        Map<String, List<ExtensionElement>> extensionMap = process.getExtensionElements();
        bindModel(po, extensionMap);
        Map<String, String> formKeyMap = new HashMap<String, String>(16);
        for (FlowElement flowElement : flowElements) {
            String formKey = null;
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                if (userTask.getFormKey() != null && userTask.getFormKey().length() > 0) {
                    formKey = userTask.getFormKey();
                }
            }
            if (!StringUtils.isEmpty(formKey) && !formKeyMap.containsKey(formKey)) {
                String formKeyDefinition = formKey.replace(".form", "");
                HmPagePO form = hmPageRepository.getById(Long.parseLong(formKeyDefinition));
                if (!StringUtils.isEmpty(form.getJson())) {
                    byte[] formJson = form.getJson().getBytes(StandardCharsets.UTF_8);
                    ByteArrayInputStream bi = new ByteArrayInputStream(formJson);
                    deploymentBuilder.addInputStream(formKey, bi);
                    formKeyMap.put(formKey, formKey);
                } else {
                    throw new FlowableObjectNotFoundException("Cannot find formJson with formKey " + formKey);
                }
            }
        }
        deploymentBuilder.name(resourceName);
        Deployment deployment = deploymentBuilder.deploy();
        return deployment;
    }


    private void bindModel(HmProcessPO po, Map<String, List<ExtensionElement>> extensionMap) {
        List<ExtensionElement> list = extensionMap.get(BpmnConstant.FIELD);
        if (CollectionUtils.isEmpty(list)) {
            throw new ErrorKeyException(I18nKeyEnum.PROCESS_DEFINITION_MISSING_MODEL_BINDING);
        }
        ExtensionElement extensionElement = list.get(0);
        if (BpmnConstant.FIELD.equals(extensionElement.getName())) {
            String value = extensionElement.getAttributeValue(null, BpmnConstant.NAME);
            if (BpmnConstant.MODEL_FIELD.equals(value)) {
                Map<String, List<ExtensionElement>> childExtensionMap = extensionElement.getChildElements();
                List<ExtensionElement> elementList = childExtensionMap.get(BpmnConstant.STRING);
                if (!CollectionUtils.isEmpty(elementList)) {
                    ExtensionElement stringElement = elementList.get(0);
                    if (stringElement.getName().equals(BpmnConstant.STRING)) {
                        String modelIdStr = stringElement.getElementText();
                        Long modelId = Long.parseLong(modelIdStr);
                        HmModelPO hmModel = hmModelRepository.getById(modelId);
                        po.setModelCode(hmModel.getCode());
                        hmProcessRepository.updateById(po);
                        return;
                    }
                }
            }
        }
        throw new ErrorKeyException(I18nKeyEnum.PROCESS_DEFINITION_MISSING_MODEL_BINDING);
    }

    static class ProcessFileFilter implements FileFilter {

        private final String type;

        ProcessFileFilter(String type) {
            this.type = type;
        }

        @Override
        public boolean accept(File pathname) {
            return pathname.getName().endsWith(type);
        }
    }


}
